package com.xored.glance.ui.viewers.tree;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreePathContentProvider;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreePathViewerSorter;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;

public abstract class TreeContentProvider {

	public static TreeContentProvider create(TreeViewer viewer) {
		IContentProvider provider = viewer.getContentProvider();
		if (provider instanceof ITreeContentProvider) {
			return new SimpleTreeContentProvider(viewer);
		} else if (provider instanceof ITreePathContentProvider) {
			return new TreePathContentProvider(viewer);
		}
		return null;
	}

	private TreeViewer viewer;

	public TreeContentProvider(TreeViewer viewer) {
		this.viewer = viewer;
	}

	public Object[] getRoots() {
		Object input = viewer.getInput();
		Object[] result = doGetRoots(input);
		result = organize(null, result);
		return savePath(null, result);
	}

	public Object[] getElements(Object parent) {
		TreePath path = elementToPath.get(parent);
		Object[] result = doGetElement(path);
		result = organize(null, result);
		return savePath(path, result);
	}

	private Object[] organize(TreePath parent, Object[] kids) {
		return sort(parent, filter(parent, kids));
	}

	private Object[] sort(TreePath parent, Object[] kids) {
		ViewerComparator comparator = viewer.getComparator();
		if (parent != null && comparator instanceof TreePathViewerSorter) {
			TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator;
			tpvs.sort(viewer, parent, kids);
		} else if (comparator != null) {
			comparator.sort(viewer, kids);
		}
		return kids;
	}

	private Object[] filter(Object parent, Object[] kids) {
		ViewerFilter[] filters = viewer.getFilters();
		for (int i = 0; i < filters.length; i++) {
			ViewerFilter filter = filters[i];
			kids = filter.filter(viewer, parent, kids);
		}
		return kids;
	}

	private Object[] savePath(TreePath parent, Object[] elements) {
		int size = parent == null ? 0 : parent.getSegmentCount();
		for (int i = 0; i < elements.length; i++) {
			Object[] segments = new Object[size + 1];
			if (size > 0) {
				for (int j = 0; j < size; j++) {
					segments[j] = parent.getSegment(j);
				}
			}
			segments[size] = elements[i];
			elementToPath.put(elements[i], new TreePath(segments));
		}
		return elements;
	}

	private Map<Object, TreePath> elementToPath = new HashMap<Object, TreePath>();

	protected abstract Object[] doGetElement(TreePath parent);

	protected Object[] doGetRoots(Object input) {
		IStructuredContentProvider provider = (IStructuredContentProvider) viewer
				.getContentProvider();
		return provider.getElements(input);
	}

	public static class SimpleTreeContentProvider extends TreeContentProvider {

		ITreeContentProvider provider;

		public SimpleTreeContentProvider(TreeViewer viewer) {
			super(viewer);
			this.provider = (ITreeContentProvider) viewer.getContentProvider();
		}

		@Override
		protected Object[] doGetElement(TreePath parent) {
			return provider.getChildren(parent.getLastSegment());
		}

	}

	public static class TreePathContentProvider extends TreeContentProvider {

		ITreePathContentProvider provider;

		public TreePathContentProvider(TreeViewer viewer) {
			super(viewer);
			this.provider = (ITreePathContentProvider) viewer
					.getContentProvider();
		}

		@Override
		protected Object[] doGetElement(TreePath parent) {
			return provider.getChildren(parent);
		}

	}

}
